package experiment;
/**
 *  Generates the xml experiment file
 *
 *  
 *  @author NetLogo
 */



import java.io.FileWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Iterator;

import java.util.List;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;

/*** import org.nlogo.agent.Dump; ***/
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;


public strictfp class ProtocolSaver
{

	// this class is not instantiable
	private ProtocolSaver() { throw new IllegalStateException() ; }

	// this is based on the sample code all the way at the bottom of
	// http://www.javazoom.net/services/newsletter/xmlgeneration.html
	// don't worry if you don't understand all the details, because I
	// don't either
	// - ST 12/18/04

	// it's a bit ugly to return a String instead of writing to a
	// PrintWriter, but the assumption is that we're never going to be
	// writing huge amounts of data here so slinging Strings around is
	// actually OK - ST 2/23/04

	public static String save( java.util.Collection protocols, String filename )
	{
		// There is a whole chuck to handle here about the filename !!!
		// Starting Changes DK 1/2/09
		try
		{
			Writer out = null;
			try {
				out = new FileWriter(filename);
				String protocolHeader = new String(
				"<?xml version=\"1.0\" encoding=\"us-ascii\"?>\n<!DOCTYPE experiments SYSTEM \"behaviorspace.dtd\">\n");
				out.write(protocolHeader);
			} 
			catch (IOException e) {
				e.printStackTrace();}
			StreamResult streamResult = new StreamResult( out ) ;
			SAXTransformerFactory tf =
				(SAXTransformerFactory) TransformerFactory.newInstance();
			try
			{
				tf.setAttribute( "indent-number" , new Integer( 2 ) ) ;
			}
			// on Java 1.4, this exception will occur; so we ignore
			// it and set the indent amount differently, below - ST 1/19/05
			catch( IllegalArgumentException ex )
			{
/***				org.nlogo.util.Exceptions.ignore( ex ) ; ***/
			}
			TransformerHandler hd = tf.newTransformerHandler() ;
			Transformer transformer = hd.getTransformer() ;
			transformer.setOutputProperty
				( OutputKeys. OMIT_XML_DECLARATION , "yes" ) ;
			transformer.setOutputProperty
				( OutputKeys.INDENT , "yes" ) ;
 
			// this works on Java 1.4.2 but not 1.5; code for the
			// the Java 1.5 case is above - ST 1/19/05
			transformer.setOutputProperty
				( "{http://xml.apache.org/xalan}indent-amount" , "2" ) ;
			// and this one works on Java 1.4.1; I'm not actually sure if
			// this and the last one are actually both necessary, but
			// let's play it safe - ST 1/25/05
			transformer.setOutputProperty
				( "{http://xml.apache.org/xslt}indent-amount" , "2" ) ;

			hd.setResult( streamResult ) ;
			hd.startDocument() ;
			AttributesImpl attributes = new AttributesImpl() ;
			hd.startElement( "" , "" , "experiments" , attributes ) ;
			for( Iterator it = protocols.iterator() ; it.hasNext() ; )
			{
				attributes.clear() ;
				Protocol protocol = (Protocol) it.next() ;
				attributes.addAttribute
					( "" , "" , "name" , "CDATA" , protocol.name ) ;
				attributes.addAttribute
					( "" , "" , "repetitions" , "CDATA" ,
					  Integer.toString( protocol.repetitions ) ) ;
				attributes.addAttribute
					( "" , "" , "runMetricsEveryStep" , "CDATA" ,
					  Boolean.toString( protocol.runMetricsEveryStep ) ) ;
				hd.startElement( "" , "" , "experiment" , attributes ) ;

				// setup
				if( ! protocol.setupCommands.trim().equals( "" ) )
				{
					hd.startElement( "" , "" , "setup" , noAttributes() ) ;
					hd.characters( protocol.setupCommands.toCharArray() ,
								   0 , protocol.setupCommands.length() ) ;
					hd.endElement( "" , "" , "setup" ) ;
				}

				// go
				if( ! protocol.goCommands.trim().equals( "" ) )
				{
					hd.startElement( "" , "" , "go" , noAttributes() ) ;
					hd.characters( protocol.goCommands.toCharArray() ,
								   0 , protocol.goCommands.length() ) ;
					hd.endElement( "" , "" , "go" ) ;
				}

				// final
				if( ! protocol.finalCommands.trim().equals( "" ) )
				{
					hd.startElement( "" , "" , "final" , noAttributes() ) ;
					hd.characters( protocol.finalCommands.toCharArray() ,
								   0 , protocol.finalCommands.length() ) ;
					hd.endElement( "" , "" , "final" ) ;
				}

				// timeLimit
				if( protocol.hasTimeLimit )
				{
					hd.startElement
						( "" , "" , "timeLimit" ,
						  oneAttribute( "steps" ,
										Integer.toString( protocol.timeLimit ) ) ) ;
					hd.endElement( "" , "" , "timeLimit" ) ;
				}					

				// exitCondition
				if( protocol.hasExitCondition )
				{
					hd.startElement
						( "" , "" , "exitCondition" , noAttributes() ) ;
					hd.characters( protocol.exitCondition.toCharArray() ,
								   0 , protocol.exitCondition.length() ) ;
					hd.endElement( "" , "" , "exitCondition" ) ;
				}					

				// metrics
				for( int i = 0 ; i < protocol.metrics.length ; i++ )
				{
					hd.startElement
						( "" , "" , "metric" , noAttributes() ) ;
					hd.characters( protocol.metrics[ i ].toCharArray() ,
								   0 , protocol.metrics[ i ].length() ) ;
					hd.endElement( "" , "" , "metric" ) ;
				}

				// value sets
				for( Iterator it2 = protocol.valueSets.keySet().iterator() ;
					 it2.hasNext() ; )
				{
					String name = (String) it2.next() ;
					ValueSet valueSet =
						(ValueSet) protocol.valueSets.get( name ) ;
					AttributesImpl attributes2 = new AttributesImpl() ;
					attributes2.addAttribute
						( "" , "" , "variable" , "CDATA" , name ) ;
					if( valueSet.isStepped )
					{
						attributes2.addAttribute
							( "" , "" , "first" , "CDATA" ,
							  Dumpnumber( valueSet.first ) ) ;
						attributes2.addAttribute
							( "" , "" , "step" , "CDATA" ,
							  Dumpnumber( valueSet.step ) ) ;
						attributes2.addAttribute
							( "" , "" , "last" , "CDATA" ,
							  Dumpnumber( valueSet.last ) ) ;
						hd.startElement
							( "" , "" , "steppedValueSet" , attributes2 ) ;
						hd.endElement
							( "" , "" , "steppedValueSet" ) ;
					}
					else
					{
						hd.startElement
							( "" , "" , "enumeratedValueSet" , attributes2 ) ;
						for( Iterator it3 = valueSet.values.iterator() ;
							 it3.hasNext() ; )
						{
							AttributesImpl attributes3 = new AttributesImpl() ;
							attributes3.addAttribute
								( "" , "" , "value" , "CDATA " ,
								  DumplogoObject
								  ( it3.next() , true , false ) ) ;
							hd.startElement
								( "" , "" , "value" , attributes3 ) ;
							hd.endElement
								( "" , "" , "value" ) ;
						}
						hd.endElement
							( "" , "" , "enumeratedValueSet" ) ;
					}
				}

				// done
				hd.endElement( "" , "" , "experiment" ) ;
			}
			hd.endElement( "" , "" , "experiments" ) ;
			hd.endDocument() ;
			return out.toString().replaceAll( "\r\n" , "\n" ) ;
		}
		catch( SAXException ex )
		{
/***			org.nlogo.util.Exceptions.handle( ex ) ; ***/
			return "" ;
		}
		catch( TransformerConfigurationException ex )
		{
/***			org.nlogo.util.Exceptions.handle( ex ) ; ***/
			return "" ;
		}
	}

	// helper
	private static Attributes noAttributes()
	{
		return new AttributesImpl() ;
	}

	// helper
	private static Attributes oneAttribute( String name , String value )
	{
		AttributesImpl result = new AttributesImpl() ;
		result.addAttribute( "" , "" , name , "CDATA" , value ) ;
		return result ;
	}

    // DK added ALL the code below from org.nlogo.agent.Dump to avoid
    // dependencies with NetLogo.

    // DK Copy & Paste from org.nlogo.agent.Dump
	public static String Dumpnumber( double d )
	{
		long l = (long) d ;
		return
			( l == d &&
			  l >= -9007199254740992L &&
			  l <= 9007199254740992L )
			? Long.toString( l )
			: Double.toString( d ) ;
	}

    // DK Copy & Paste & Simplify from org.nlogo.agent.Dump
    public static String DumplogoObject( Object obj , boolean readable , boolean exporting )
	{
         if( obj instanceof Integer )
		{
			throw new IllegalArgumentException( "Integer: " + obj ) ;
		}
		else if( obj instanceof Boolean )
		{
			return obj.toString() ;
		}
		else if( obj instanceof Double )
		{
			return Dumpnumber( (Double) obj ) ;
		}
		else if( obj instanceof String )
		{
			if( readable )
			{
				return "\"" + escapeString( (String) obj ) + "\"" ;
			}
			else
			{
				return (String) obj ;
			}
		}
		else if( obj instanceof List )
		{
			return list( (List<?>) obj , readable, exporting ) ;
		}
		else
		{
			return "<" + obj.getClass().getName() + ">" ;
		}
	}


    public static String escapeString( String st )
	{
		StringBuilder buff = new StringBuilder() ;

		for( int i = 0 ; i < st.length() ; i++ )
		{
			char ch = st.charAt( i ) ;
			switch( ch )
			{
				case '\n':
					buff.append( "\\n" ) ;
					break;
				case '\r':
					buff.append( "\\r" ) ;
					break;
				case '\t':
					buff.append( "\\t" ) ;
					break;
				case '\\':
					buff.append( "\\\\" ) ;
					break;
				case '\"':
					buff.append( "\\\"" ) ;
					break;
				default:
					buff.append( ch ) ;
			}
		}

		return buff.toString() ;
	}

    public static String list( List<?> list, boolean readable, boolean exporting )
	{
		return iterator( list.iterator(), readable , exporting ) ;
	}

    	public static String iterator(Iterator<?> iter, boolean readable, boolean exporting)
	{
		return iterator( iter , "[" , "]" , " " , readable, exporting ) ;
	}

	public static String iterator(Iterator<?> iter, String prefix, String suffix, String delimiter, boolean readable, boolean exporting)
	{
		StringBuilder buff=new StringBuilder();
		buff.append( prefix ) ;
		boolean firstOne = true ;
		while( iter.hasNext() )
		{
			if( firstOne )
			{
				firstOne = false ;
			}
			else
			{
				buff.append( delimiter ) ;
			}
			buff.append(logoObject(iter.next(), readable, exporting));
		}
		buff.append( suffix ) ;
		return buff.toString() ;
	}

public static String logoObject( Object obj , boolean readable , boolean exporting )
{

	if( obj instanceof Integer )
	{
		throw new IllegalArgumentException( "Integer: " + obj ) ;
	}
	else if( obj instanceof Boolean )
	{
		return obj.toString() ;
	}
	else if( obj instanceof Double )
	{
		return number( (Double) obj ) ;
	}
	else if( obj instanceof String )
	{
		if( readable )
		{
			return "\"" + escapeString( (String) obj ) + "\"" ;
		}
		else
		{
			return (String) obj ;
		}
	}
	else if( obj instanceof List )
	{
		return list( (List<?>) obj , readable, exporting ) ;
	}
	else
	{
		return "<" + obj.getClass().getName() + ">" ;
	}
}
	public static String number( Double obj )
	{
		// If there is some more efficient way to test
		// whether a double has no fractional part and
		// lies in IEEE 754's exactly representable range,
		// I would love to know about it. - ST 5/31/06
		double d = obj.doubleValue() ;
		long l = (long) d ;
		return
			( l == d &&
			  l >= -9007199254740992L &&
			  l <= 9007199254740992L )
			? Long.toString( l )
			: Double.toString( d ) ;
	}

	public static String number( double d )
	{
		long l = (long) d ;
		return
			( l == d &&
			  l >= -9007199254740992L &&
			  l <= 9007199254740992L )
			? Long.toString( l )
			: Double.toString( d ) ;
	}




}
